#include <limits.h>
#include <mpi.h>
#include <omp.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#include "utils.h"


int alloc_matrix(struct matrix_t** mat, long m, long n)
{
	*mat = (struct matrix_t*) malloc(sizeof(struct matrix_t));
//	(*mat)->mat = (long*) malloc(sizeof(long)*m*n);
	MPI_Alloc_mem(sizeof(long)*m*n, MPI_INFO_NULL, &((*mat)->mat));
	(*mat)->m = m;
	(*mat)->n = n;

	if (mat == NULL) return -1;
	return 0;
}


int alloc_matrix_b(struct matrix_t** mat, long m, long n)
{
	return alloc_matrix(mat, n, m);
}


int dealloc_matrix(struct matrix_t* mat)
{
//	free(mat->mat);
	MPI_Free_mem(mat->mat);
	free(mat);

	return 0;
}


long get_elem(struct matrix_t* mat, long i, long j)
{
	if ((i >= mat->m) || (j >= mat->n)) {
		fprintf(stderr, "get_elem: Index out of bounds\n");
		fprintf(stderr, "[i=%ld, j=%ld, m=%ld, n=%ld]\n", i, j, mat->m, mat->n);
		return LONG_MAX;
	}

	return *(mat->mat + i*mat->n + j);
}


long get_elem_b(struct matrix_t* mat, long i, long j)
{
	return get_elem(mat, j, i);
}


int set_elem(struct matrix_t* mat, long i, long j, long val)
{
	if ((i >= mat->m) || (j >= mat->n)) {
		fprintf(stderr, "set_elem: Index out of bounds\n");
		fprintf(stderr, "[i=%ld, j=%ld, m=%ld, n=%ld]\n", i, j, mat->m, mat->n);
		return -1;
	}

	*(mat->mat + i*mat->n + j) = val;

	return 0;
}


int set_elem_b(struct matrix_t* mat, long i, long j, long val)
{
	return set_elem(mat, j, i, val);
}


int get_matrix_dim(MPI_File f, struct matrix_t** dim)
{
	char* buf;
	int i, counter;
	MPI_Status status;
	long m=0, n=0;
	int err=0;

	buf = (char*) malloc(BLK_SIZE*sizeof(char));
	if (buf == NULL) return -1;
	*dim = (struct matrix_t*) malloc(sizeof(struct matrix_t));
	if (*dim == NULL) return -1;

	err = MPI_File_seek(f, 0, MPI_SEEK_SET);
	err |= MPI_File_read(f, buf, BLK_SIZE, MPI_CHAR, &status);
	MPI_Get_count(&status, MPI_CHAR, &counter);
	while (counter > 0) {
		for (i = 0; i < counter; ++i) {
			if (buf[i] == ' ') {
				if (m == 0) ++n;
			} else if (buf[i] == '\n') {
				if (++m == 1) ++n;
			}
		}
		err |= MPI_File_read(f, buf, BLK_SIZE, MPI_CHAR, &status);
		MPI_Get_count(&status, MPI_CHAR, &counter);
	}

	(*dim)->m = m; 
	(*dim)->n = n;

	free(buf);
	return err;
}


int read_matrix(
	char* filename,  
	struct matrix_t** mat, 
	struct MPI_Info_t* info, 
	int mat_id)
{
	MPI_File f;
	char buf[BLK_SIZE];
	char digits_buf[MAX_LEN_NUM];
	struct matrix_t* dim;
	short it=0;
	int m=0, n=0, j=0;
	int decide;	// Toma el valor del parametro a considerar para armar la submatriz.
			// Filas en caso de ser la matriz A. Columnas, en el caso de B.
	int ri, rf;	// Intervalo de la matriz a guardar en memoria.
	int i, counter;
	MPI_Status status;
	int err=0;

	err |= MPI_File_open(
		MPI_COMM_WORLD, 
		filename, 
		MPI_MODE_RDONLY,
		MPI_INFO_NULL,
		&f);
	MPI_File_set_view(
		f, 0,
		MPI_CHAR, MPI_CHAR,
		"external32", MPI_INFO_NULL);
	if (err != 0) return err;

	get_matrix_dim(f, &dim);
	rf = (mat_id == MAT_A) ? dim->m : dim->n;
	rf /= info->num_procs;
	err |= (mat_id == MAT_A) ? alloc_matrix(mat, rf, dim->n) : alloc_matrix(mat, dim->m, rf);
	ri = rf * info->proc_id;
	rf += ri;

	MPI_File_seek(f, 0, MPI_SEEK_SET);
	err |= MPI_File_read(f, buf, BLK_SIZE, MPI_CHAR, &status);
	MPI_Get_count(&status, MPI_CHAR, &counter);
	while (counter > 0) {
		for (i = 0; i < counter; ++i) {
			decide = (mat_id == MAT_A) ? m : n;
			if (buf[i] == ' ') {
				if ( (decide >= ri) && (decide < rf) ) {
					digits_buf[it] = '\0';
					it = 0;
					if (mat_id == MAT_A) set_elem(*mat, j, n, atol(digits_buf));
					if (mat_id == MAT_B) set_elem(*mat, m, j++, atol(digits_buf));
				} else if (mat_id == MAT_B) {
					j = 0;
				}
				++n;
			} else if (buf[i] == '\n') {
				if ( (decide >= ri) && (decide < rf) ) {
					digits_buf[it] = '\0';
					it = 0;
					if (mat_id == MAT_A) set_elem(*mat, j++, n, atol(digits_buf));
					if (mat_id == MAT_B) set_elem(*mat, m, j, atol(digits_buf));
				}
				++m; n = 0;
			} else if ( (decide >= ri) && (decide < rf) ) {
				digits_buf[it] = buf[i];
				++it;
			}
		}

		if (it == MAX_LEN_NUM) {
			fprintf(stderr, "read_matrix: \n");
			fprintf(stderr, "Number too large for it to be read.\n");
			fprintf(stderr, "The max. length is %d.\n", MAX_LEN_NUM);
			return -1;
		}
	
		err |= MPI_File_read(f, buf, BLK_SIZE, MPI_CHAR, &status);
		MPI_Get_count(&status, MPI_CHAR, &counter);
	}

	MPI_File_close(&f);
	return err;
}


int sub_matrix(
	struct matrix_t* mat, 
	struct matrix_t** tmat, 
	long i1, long i2,
	long j1, long j2)
{
	int itr, itc;
	int i, j;
	int err=0;

	err |= alloc_matrix(tmat, i2-i1+1, j2-j1+1);

	for (itr=i1, i=0; itr <= i2; ++itr, ++i) {
		for (itc=j1, j=0; itc <= j2; ++itc, ++j) {
			err |= set_elem(*tmat, i, j, get_elem(mat, itr, itc));
		}
	}

	return err;
}


int mac(long* a, long* b, long n, long* ans)
{
	int i;
	
	*ans = 0;
	for (i = n-1; i >= 0; --i) {
		*ans += a[i] * b[i];
	}

	return 0;
}


int mat_prod(struct matrix_t* a, struct matrix_t* b, long i, long j, long* ans)
{
	struct matrix_t* a_i;
	struct matrix_t* b_j;
	int k;

	*ans = 0;
	for (k = 0; k < a->n; ++k) {
		*ans += get_elem(a, i, k) * get_elem_b(b, k, j);
	}	
	//sub_matrix(a, &a_i, i, i, 0, a->n-1);
	//sub_matrix(b, &b_j, 0, b->m-1, j, j);
	//mac(a_i->mat, b_j->mat, a->n, ans);

	//dealloc_matrix(a_i);
	//dealloc_matrix(b_j);

	return 0;
}


int diag_calc(struct algorithm_data* info)
{
	return calc(info, info->b, info->info->proc_id);
}


int calc(struct algorithm_data* info, struct matrix_t* b, int serverpid)
{
	long chunk_size=info->a->m/NUM_CORES;
	long ans;
	int i, j;
	int err=0;

	//chunk_size >>= 2;

#pragma omp parallel default(shared) private (ans, i, j)
{
#pragma omp for schedule(dynamic, chunk_size) nowait
	for (i = 0; i < info->a->m; ++i) {
		for (j = 0; j < b->m; ++j) {
			err |= mat_prod(info->a, b, i, j, &ans);
			err |= set_elem(info->c, i, j+serverpid*b->m, ans);
		}
	}
}

	return err;
}


int get_mpi_info(struct MPI_Info_t* info)
{
	int err=0;
	int num_comms;
	int i, procs[2];
	MPI_Group group, new_group;
	
	if (info == NULL) {
		info = (struct MPI_Info_t*) malloc(sizeof(struct MPI_Info_t));
		if (info == NULL) return -1;
	}

	err |= MPI_Comm_size(MPI_COMM_WORLD, &(info->num_procs));
	err |= MPI_Comm_rank(MPI_COMM_WORLD, &(info->proc_id));

	num_comms = info->num_procs - 1;
	info->comms = (MPI_Comm*) malloc(sizeof(MPI_Comm)*num_comms);
	if (info->comms == NULL) return -1;

	MPI_Comm_group(MPI_COMM_WORLD, &group);
	info->group = &group;
	for (i = 0; i < num_comms; ++i) {
		procs[0] = 0; procs[1] = i + 1;
		err |= MPI_Group_incl(group, 2, procs, &new_group);
		err |= MPI_Comm_create(MPI_COMM_WORLD, new_group, info->comms+i);
	}

	return err;
}
